#ifndef ABSTRACT_OBJECT_H
#define ABSTRACT_OBJECT_H

#include <string>
#include <map>
#include "lua.hpp"
#include "InterData.h"
#include "Logger.h"

class Sheet;
class AbstractObject;

class AOPtrSled
{
public:
	AbstractObject* _target;
	int _possessiveRefs;
	int _weakRefs;
	AOPtrSled();
	AOPtrSled(AbstractObject* target);
};

class AbstractObject
{
public:
	// Nonvirtual stuff
	AbstractObject();

	AOPtrSled* getPtrSled() const;

	void setMetaData(std::string name, void *data);
	void* getMetaData(std::string name);
	
	void setLuaRef(int lr);
	int getLuaRef();
	
	void setSheet(Sheet* sh);
	Sheet* getSheet();

	// Virtual stuff

	// Return the c++ type id
	virtual int getCppTypeId();

	// Returns whether this can be cast to a given type
	virtual bool isType(int id);

	// Return the c++ type as a string 
	virtual std::string getCppTypeName();

	// Return the lua type as a string
	virtual std::string getLuaTypeName();
	virtual void setLuaTypeName(std::string newname);

	// returns the 'name' of this thing (however it wants to call itself)
	virtual std::string getName();
	virtual void setName(std::string newname);

	// returns a description of this thing
	virtual std::string getDescription();

	// create another one of these
	virtual AbstractObject* newInstance();

	// init & update
	virtual void init();
	virtual void update(double dt);

	// interact
	virtual IData interact(std::string interactionName, IData& interData);

	// push its static functions into the given lua table
	virtual void pushStaticFuncs(lua_State* L);
	virtual void pushSelf(lua_State* L);

	virtual ~AbstractObject();

private:
	std::map<std::string, void *> _metaData;
	AOPtrSled* _sled;
	int _luaref;
	Sheet* _sh;
	std::string _luaname;
	std::string _name;
	bool _noUpdateWarningSuppression;
};


template <class T>
class StrongAOPtr;

typedef StrongAOPtr<AbstractObject> AnyStrongAOPtr;


template <class T>
class AOPtr;

typedef AOPtr<AbstractObject> AnyAOPtr;


// convenience type
template <class T>
class StrongAOPtr
{
	friend class AOPtr<T>;

private:
	AOPtrSled* _target;

	void releaseTarget()
	{
		if(_target)
		{
			_target->_possessiveRefs -= 1;
			//LOG(logDEBUG) << "StrongAOPTR RELEASE: " << _target->_weakRefs << " " << _target->_possessiveRefs;
			if(_target->_possessiveRefs <= 0 && _target->_weakRefs <= 0)
			{
				if(_target->_target)
					delete _target->_target;
				else
					delete _target;
			}
			else if(_target->_possessiveRefs <= 0)
				delete _target->_target;
		}
		_target = NULL;
	}

	void acquireTarget()
	{
		if(_target)
		{
			_target->_possessiveRefs += 1;
			//LOG(logDEBUG) << "StrongAOPTR ACQUIRE: " << _target->_weakRefs << " " << _target->_possessiveRefs;
		}
	}
public:
	StrongAOPtr()
	{
		_target = NULL;
	}

	AOPtrSled* getTarget()
	{
		return _target;
	}

	StrongAOPtr(AbstractObject* target)
	{
		if(target)
			_target = target->getPtrSled();
		else
			_target = NULL;
		acquireTarget();
	}

	StrongAOPtr(const StrongAOPtr<T>& rhs)
	{
		_target = rhs._target;
		acquireTarget();
	}

	StrongAOPtr<T>& operator=(const AbstractObject* rhs)
	{
		if(rhs)
		{
			if(rhs->getPtrSled() != _target)
			{
				releaseTarget();
				_target = rhs->getPtrSled();
				acquireTarget();
			}
		}
		else
		{
			releaseTarget();
			_target = NULL;
		}
		return *this;
	}

	StrongAOPtr<T>& operator=(const StrongAOPtr<T> rhs)
	{
		if(rhs._target != _target)
		{
			releaseTarget();
			_target = rhs._target;
			acquireTarget();
		}
		return *this;
	}

	// Dereference and ->
	T* operator*()
	{
		if(_target)
			return (T*)(_target->_target);
		else
			return NULL;
	}

	T* operator->()
	{
		if(_target)
			return (T*)(_target->_target);
		else
			return NULL;
	}

	AnyStrongAOPtr any()
	{
		return (AnyStrongAOPtr)(*this);
	}

	~StrongAOPtr()
	{
		releaseTarget();
	}
};

template <class T>
class AOPtr
{
	friend class StrongAOPtr<T>;

private:
	void acquireTarget()
	{
		if(_target)
		{
			_target->_weakRefs += 1;
			//LOG(logDEBUG) << "AOPTR ACQUIRE: " << _target->_weakRefs << " " << _target->_possessiveRefs;
		}
	}

	void releaseTarget()
	{
		if(_target)
		{
			_target->_weakRefs -= 1;
			//LOG(logDEBUG) << "AOPTR RELEASE: " << _target->_weakRefs << " " << _target->_possessiveRefs;
			if(_target->_possessiveRefs <= 0 && _target->_weakRefs <= 0)
			{
				if(_target->_target)
				{
					//LOG(logWARNING) << "What? This should have already been deleted.";
					delete _target->_target;
				}
				else // !_target->_target (just delete sled)
					delete _target;
			}
			else if(_target->_possessiveRefs <= 0 && _target->_target)
			{
				//LOG(logWARNING) << "What? This should have been deleted already!";
				//if(_target->_target == NULL)
				//	LOG(logDEBUG) << "(I am being dumb)";
				delete _target->_target;
			}
		}
		_target = NULL;
	}

	void forceInitialize()
	{
		_target = NULL;
	}

public:
	AOPtr() // same as AOPtr(NULL);
	{
		_target = NULL;
	}

	AOPtr(AbstractObject* target)
	{
		if(target)
			_target = target->getPtrSled();
		else
			_target = NULL;
		acquireTarget();
	}

	AOPtr(const AOPtr& rhs)
	{
		_target = rhs._target;
		acquireTarget();
	}

	// Possible to go from Possessive -> regular, but not vice versa
	// (although StrongAOPtr(*AOPtr) is possible but not recommended)
	AOPtr(const StrongAOPtr<T>& rhs)
	{
		_target = rhs._target;
		acquireTarget();
	}

	// Assignment
	AOPtr& operator=(const AOPtr& rhs)
	{
		if(rhs._target != _target)
		{
			releaseTarget();
			_target = rhs._target;
			acquireTarget();
		}
		return *this;
	}

	AOPtr& operator=(const AnyStrongAOPtr& rhs)
	{
		if(rhs._target != _target)
		{
			releaseTarget();
			_target = rhs._target;
			acquireTarget();
		}
		return *this;
	}

	AOPtr& operator=(const AbstractObject* rhs)
	{
		if(rhs == NULL)
		{
			releaseTarget();
			_target = NULL;
		}
		else if(rhs->getPtrSled() != _target)
		{
			releaseTarget();
			_target = rhs->getPtrSled();
			acquireTarget();
		}
		return *this;
	}

	// equality
	bool operator==(const AOPtr& rhs) const
	{
		return _target == rhs._target;
	}

	// Dereference and ->
	AbstractObject* operator*()
	{
		if(_target)
			return _target->_target;
		else
			return NULL;
	}

	AbstractObject* operator->()
	{
		if(_target)
			return _target->_target;
		else
			return NULL;
	}

	static int delete_self(lua_State* L)
	{
		AnyAOPtr* pt = (AnyAOPtr*)lua_touserdata(L, 1);
		(*pt) = NULL;
		return 0;
	}

	// Push yourself
	void push_AOPtr(lua_State* L)
	{
		AnyAOPtr* tp = (AnyAOPtr*)lua_newuserdata(L, sizeof(AnyAOPtr));
		tp->forceInitialize();
		*tp = *this;

		// create metatable
		lua_newtable(L);
		lua_pushcfunction(L, delete_self);
		lua_setfield(L, -2, "__gc");
		lua_setmetatable(L, -2);
	}

	void pushTarget(lua_State* L)
	{
		if(_target == NULL || _target->_target == NULL)
			lua_pushnil(L);
		else
			_target->_target->pushSelf(L);
	}

	AnyAOPtr any()
	{
		return (AnyAOPtr)(*this);
	}

	~AOPtr()
	{
		releaseTarget();
	}

private:
	AOPtrSled* _target;
};

#endif
